package taweb.flex.air.fexBrowser.struct
{
	import mx.collections.ArrayCollection;
	import mx.states.OverrideBase;
	import mx.utils.URLUtil;
	
	import taweb.flex.air.fexBrowser.tree.TreeItem;
	import taweb.flex.air.fexBrowser.tree.TreeLeaf;
	import taweb.flex.air.fexBrowser.tree.TreeNode;
	import taweb.flex.air.fexBrowser.util.FexURLUtil;
	import taweb.flex.air.fexBrowser.util.KeyVal;
	import taweb.flex.air.fexBrowser.util.Tools;

	/**
	 * Un noeud d'un URL.
	 */
	public class URLNode
	{
		/**
		 * Le nom de noeud.
		 */		
		private var name:String;
		
		/**
		 * Le noeud parent.
		 */		
		private var parent:URLNode = null;
		
		/**
		 * Les noeuds fils.
		 */
		private var children:Array;
		
		/**
		 * Les liens poitant sur le noeud.
		 */
		private var links:Array;
		
		/**
		 * L'instance unique de noeud root (Singleton).
		 */
		private static var instance:URLNode = null;
		
		/**
		 * Le constructeur.
		 * @param privateKey Jeton privé empêchant l'instantiation externe via le "new"
		 * @use URLNode.Instance
		 * @param name Le nom de noeud.
		 * @param parent Le noeud parent.
		 */		
		public function URLNode(privateKey:PrivateKey, name:String, parent:URLNode)
		{
			// Forcer le passage de la clé privé
			if(privateKey == null)
			{
				throw new Error("Error: Instantiation failed: Use URLNode.Instance instead of new.");
			}
			
			this.children = new Array();
			this.links = new Array();
			
			this.name = name;
			this.parent = parent;
		}
		
		
		/**
		 * Retounre le noeud racine. 
		 * @return LE noeud racine.
		 */
		public static function get Instance():URLNode
		{
			if(instance==null)
			{
				instance = new URLNode(new PrivateKey(), "[ROOT]", null);
			}
			return instance;
		}
		
		/**
		 * Retourne le nom de noeud.
		 * @return Le nom de noeud.
		 */		
		public function get getName():String
		{
			return this.name;
		}
		
		public function toString():String
		{
			return this.name;
		}
		
		/**
		 * Retourne le noeud parent.
		 * @return Le noeud parent.
		 */		
		public function get getParent():URLNode
		{
			return this.parent;
		}
		
		/**
		 * Vérifie si le noeud est racine ou pas.
		 * @return <i>true</i> si le noeud est racine, <i>false</i> sinon.
		 */		
		public function isRoot():Boolean
		{
			return this.parent == null;
		}
		
		
		/**
		 * Vérifie si le noeud est type host.<br\> 
		 * Host ça veut dire qu'il représente le noeud racine de l'URL.
		 * @return <i>true</i> si le noeud est host, <i>false</i> sinon.
		 * 
		 */
		public function isHost():Boolean
		{
			// Un noeud est host si il a la noeud racine (unique) comme parent
			return (this.parent != null) && this.parent.isRoot();
		}
		
		/**
		 * Retourne les noeuds fils.
		 * @return Les noeuds fils.
		 */		
		public function get getChildren():Array
		{
			return this.children;
		}
		
		/**
		 * Cree un nouveau noeud fils.
		 * @param name Le nom de noeud fils.
		 * @return Le noeud fils.
		 */		
		private function newChild(name:String):URLNode
		{
			// On instance le nouveau noeud fils
			var child:URLNode = new URLNode(new PrivateKey(), name, this);
			
			// et on l'ajoute aux noeuds fils
			this.children.push(child);
			return child;
		}
		
		/**
		 * Structure récursivement le lien (URL/Path) sous forme de noeuds.
		 * @param path Le chemin d'accès à la ressource.
		 * @param link Le couplet label=URL.
		 */		
		public function addPath(path:String, link:KeyVal):void
		{
			var node:URLNode = null;
			
			var nodeName:String;
			var pathReste:String = "";
			
			// On est arrivé au bout de l'URL
			if(path.length == 0)
			{
				//trace("le bout de l'URL");
				
				// On pointe le lien sur le noeud
				this.addLink(link);
			} 
			else // Sinon, il reste encore un chemin à traiter.
			{
				if(FexURLUtil.isURL(path)) // Noeud host (racine)
				{
					nodeName = FexURLUtil.getHost(path);
					pathReste = FexURLUtil.getPath(path);
					
					//trace("\n# Noeud host (racine): " + path);
				}
				else // Noeud non racine
				{
					//trace("Noeud non racine:");
					
					var pathNodes:Array = path.split("/");
					
					if(pathNodes.length >= 2) // noeud au milieu
					{
						//trace("noeud au milieu");
						
						nodeName = pathNodes[0];
						pathReste = path.substr(path.indexOf("/")+1);
					}
					else // noeud final (feuille)
					{
						nodeName = path;
						pathReste = "";
						
						//trace("noeud final (feuille)");
					}
				}
				
				// On recupère le noeud si il existe déjà
				node = this.findChild(nodeName);
				if(node==null)
				{
					// sinon on instance un nouveau noeud
					node = this.newChild(nodeName);
				}
				
				// On extrait et on traite le reste de path
				node.addPath(pathReste, link);
			}
		}
		
		/**
		 * Ajoute un lien (couplet) au noeud courant.
		 * @param link Le lien (couplet) à faire pointer sur le noeud.
		 */		
		public function addLink(link:KeyVal):void{
			
			// On recupère (par URL) le lien si il existe déjà
			var aLink:LinkNode = findLink(link.getVal);
			
			// Si un lein existe déjà avec le même URL
			if(aLink!=null)
			{
				// On signale la re-détéction de lien/URL
				aLink.incNumber();
				
				// on ajoute le label au lien récupéré 
				aLink.addLabel(link.getKey);
			}
			else
			{
				// sinon on instance un nouveau lien
				aLink = new LinkNode(link.getKey, link.getVal);
				
				// et on le fait pointer sur le noeud courant
				this.links.push(aLink);
			}
		}
		
		/**
		 * Recherche un noeud fils par son nom.
		 * @param name Le nom de noeud fils à chercher.
		 * @return Le noeud fils, sinon, <i>null</i>.
		 */		
		public function findChild(name:String):URLNode
		{
			for each (var node:URLNode in this.children) {
				if(node.getName==name)
				{
					return node;
				}
			}
			return null;
		}
		
		/**
		 * Recherche un lien par son URL.
		 * @param url L'URL de lien à chercher.
		 * @return Le lien trouvé, sinon, <i>null</i>.
		 * 
		 */		
		public function findLink(url:String):LinkNode
		{
			for each (var link:LinkNode in this.links) {
				if(link.getURL == url)
				{
					return link;
				}
			}
			return null;
		}
		
		/**
		 * Affiche/Trace toute la structure à partir de noeud nourant.
		 * @param f Le niveau courant.
		 */		
		public function print(f:int=0):void
		{
			// On cree une indentation à partir de niveau courant
			var indent:String = "";
			for(var i:int=0; i<f; i++) indent+= " ";
			
			// on affiche le noeud avec le nombre de liens pointant sur ce dernier.
			// "@" précéde le noeud racine et les autres sont précédé par un "+".
			trace(indent + (this.isRoot()?"@ ":"+ ") + this.getName + " - " + this.links.length + " link(s)");
			
			// On trace les liens pointant sur le noeud
			for each(var link:LinkNode in this.links)
			{
				link.print(indent);
			}
			
			// On traite récursivement les noeuds fils
			for each(var child:URLNode in this.children)
			{
				child.print(f+1);
			}
		}
		
		/**
		 * Retourne la structure arborescente à affiche dans le "Tree component"
		 */
		public function getTreeStruct():TreeItem
		{
			var treeNode:TreeNode = new TreeNode(this.name,Tools.getRandomColor());
			
			// Noeuds fils
			for each (var child:URLNode in this.children)
			{
				treeNode.children.push(child.getTreeStruct());
			}
			
			// Liens
			for each (var link:LinkNode in this.links)
			{
				treeNode.children.push(link.getTreeStruct());
			}

			return treeNode;
		}
		
		
		
		
		/**
		 * Supprime les liens et les noeuds fils
		 */
		public function clear():void
		{
			this.children = new Array();
			this.links = new Array();
		}
	}
}

/**
 * Class (clé) interne permettant de restreindre l'instanciation de la class principale (URLNode)
 * seulement en interne ce qui permet d'implémenter le design pattern singleton.
 * NB. ne pouvons pas utiliser un constructeur privé.
 */
internal class PrivateKey{}
